package de.hska.kundenverwaltung;

import static de.hska.util.EjbConstants.PERSISTENCE_CONTEXT_HSKA;
import static de.hska.util.EjbConstants.ROLLE_ADMIN;
import static de.hska.util.EjbConstants.ROLLE_MITARBEITER;
import static de.hska.util.EjbConstants.KUNDE_NACHNAME_PATTERN;
import static de.hska.util.LogUtil.begin;
import static de.hska.util.LogUtil.end;
import static de.hska.util.PasswordUtil.verschluesseln;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import javax.annotation.security.RolesAllowed;
import javax.ejb.EJB;
import javax.ejb.Local;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import de.hska.kundenverwaltung.db.BildException;
import de.hska.kundenverwaltung.db.Kunde;
import de.hska.kundenverwaltung.db.KundeNotFoundException;
import de.hska.kundenverwaltung.db.KundeUpdatedException;
import de.hska.kundenverwaltung.db.KundenverwaltungDao;
import de.hska.kundenverwaltung.db.KundeDeletedException;
import de.hska.util.MessagesUtil;


/**
 * Anwendungskern mit Plausibilit&auml;tspr&uuml;fungen und Aufruf der DAOs;
 * Methoden f&uuml;r MDB (peer-to-peer und publish-subscribe).
 */
@Stateless
@Local(Kundenverwaltung.class)
//JBoss-spezifische Annotation fuer Security-Domain (statt jboss.xml)
//@SecurityDomain("hska")
public class KundenverwaltungBean implements Kundenverwaltung {
	public static final Log LOG = LogFactory.getLog(KundenverwaltungBean.class);
	private static final boolean DEBUG = LOG.isDebugEnabled();
	
	@SuppressWarnings("unused")
	@PersistenceContext(name=PERSISTENCE_CONTEXT_HSKA)
	private EntityManager em;
	
	@EJB
	private KundenverwaltungDao dao;
	
	@Resource(name="maxKategorie")         // ggf. ueberschreiben in ejb-jar.xml
	private Short maxKategorie = Short.valueOf((short) 0);
	
	@Resource(name="maxRabatt")         // ggf. ueberschreiben in ejb-jar.xml
	private Float maxRabatt = Float.valueOf(0);

	@SuppressWarnings("unused")
	@PreDestroy
	private void preDestroy() {
		if (DEBUG) {
			final String msg = MessagesUtil.getMessage("log.ejbDeleted", KundenverwaltungBean.class.getSimpleName());
			LOG.debug(msg);
		}
	}

	/**
	 * {@inheritDoc}
	 */
	public short getMaxKategorie() {
		final short result = maxKategorie.shortValue();
		return result;
	}
	
	/**
	 * {@inheritDoc}
	 */
	public float getMaxRabatt() {
		final float result = maxRabatt.floatValue();
		return result;
	}

	/**
	 * {@inheritDoc}
	 */
	public Collection<Kunde> findAllKunden() {
		final List<Kunde> kunden = dao.findAllKunden();
		return kunden;
	}
	
	/**
	 * {@inheritDoc}
	 */
	public Collection<Kunde> findAllKundenId() {
		final List<Kunde> kunden = dao.findAllKundenId();
		return kunden;
	}

	/**
	 * {@inheritDoc}
	 */
	public List<Kunde> findKunden(String nachname) throws KundeNotFoundException, KundeInvalidNachnameException {
		if (!checkKundeNachname(nachname)) {
			final KundeInvalidNachnameException e = new KundeInvalidNachnameException(nachname);
			LOG.error(e.getMessage());
			throw e;
		}
		
		final List<Kunde> kunden = dao.findKundenByNachname(nachname);
		return kunden;
	}

	/**
	 * {@inheritDoc}
	 */
	public List<Kunde> findKundenByNachnamePrefix(String nachnamePrefix) {
		final List<Kunde> kunden = dao.findKundenByNachnamePrefix(nachnamePrefix);
		return kunden;
	}
	
	/**
	 * {@inheritDoc}
	 */
	public List<String> findAllNachnamen() {
		final List<String> nachnamen = dao.findAllNachnamen();
		return nachnamen;
	}
	
	/**
	 * {@inheritDoc}
	 */
	public List<Kunde> findKundenMitBestellungenUndLieferungen(String nachname, boolean mitLieferungen)
	                   throws KundeNotFoundException, KundeInvalidNachnameException {
		if (!checkKundeNachname(nachname)) {
			final KundeInvalidNachnameException e = new KundeInvalidNachnameException(nachname);
			LOG.error(e.getMessage());
			throw e;
		}
		
		final List<Kunde> kunden = dao.findKundenMitBestellungenUndLieferungenByNachname(nachname, mitLieferungen);
		return kunden;
	}

	/**
	 * {@inheritDoc}
	 */
	public Kunde findKundeById(Long id) throws KundeNotFoundException, KundeInvalidIdException {
		if (!checkKundeId(id)) {
			final KundeInvalidIdException e = new KundeInvalidIdException(id); 
			LOG.error(e.getMessage());
			throw e;
		}

		final Kunde kunde = dao.findKundeById(id);
		return kunde;
	}

	/**
	 * {@inheritDoc}
	 */
	public Kunde findKundeMitBestellungenById(Long id) throws KundeNotFoundException, KundeInvalidIdException {
		if (!checkKundeId(id)) {
			final KundeInvalidIdException e = new KundeInvalidIdException(id); 
			LOG.error(e.getMessage());
			throw e;
		}
		
		final Kunde kunde = dao.findKundeMitBestellungenById(id);
		return kunde;
	}
	
	/**
	 * {@inheritDoc}
	 */
	public Kunde findKundeByUserName(String userName) throws KundeNotFoundException {
		final Kunde kunde = dao.findKundeByUserName(userName);
		return kunde;
	}

	/**
	 * {@inheritDoc}
	 */
	@RolesAllowed(ROLLE_ADMIN)  // Eigentlich unsinnig; Security soll halt gezeigt werden
	public void deleteKunden(Collection<Kunde> kunden) throws KundeDeleteBestellungException {
		if (kunden == null || kunden.isEmpty()) {
			return;
		}

		// Constraint: Hat ein Kunde mind. 1 Bestellung?
		for (Kunde k: kunden) {
			final boolean hasBestellungen = hasBestellungen(k);
			if (hasBestellungen == true) {
				final KundeDeleteBestellungException e = new KundeDeleteBestellungException(k);
				LOG.error(e.getMessage());
				throw e;
			}
		}

		// Kundendaten loeschen
		dao.deleteKunden(kunden);
	}

	/**
	 * {@inheritDoc}
	 */
	@RolesAllowed(ROLLE_ADMIN)  // Eigentlich unsinnig; Security soll halt gezeigt werden
	public void deleteKundeById(Long kundeId) throws KundeDeleteBestellungException, KundeInvalidIdException {
		if (!checkKundeId(kundeId)) {
			final KundeInvalidIdException e = new KundeInvalidIdException(kundeId); 
			LOG.error(e.getMessage());
			throw e;
		}
		
		Kunde kunde = null;
		try {
			kunde = dao.findKundeMitBestellungenById(kundeId);
		}
		catch (KundeNotFoundException e) {
			return;
		}

		final boolean hasBestellungen = hasBestellungen(kunde);
		if (hasBestellungen == true) {
			final KundeDeleteBestellungException e = new KundeDeleteBestellungException(kunde);
			LOG.error(e.getMessage());
			throw e;
		}

		// Kundendaten loeschen
		final Collection<Kunde> kunden = new ArrayList<Kunde>(1);
		kunden.add(kunde);
		
		dao.deleteKunden(kunden);
	}

	/**
	 * {@inheritDoc}
	 */
	@RolesAllowed({ROLLE_MITARBEITER, ROLLE_ADMIN})  // Eigentlich unsinnig; Security soll halt gezeigt werden
	public Collection<Kunde> createKunden(Collection<Kunde> kunden) throws KundeCreateAdresseException, KundeDuplikatException {
		if (kunden == null || kunden.isEmpty()) {
			return kunden;
		}

		// Werden alle Constraints beim Einfuegen gewahrt?
		// Passwort verschluesseln
		for (Kunde k: kunden) {
			final boolean hasAdresse = hasAdresse(k);
			if (hasAdresse == false) {
				final KundeCreateAdresseException e = new KundeCreateAdresseException(k.getNachname(), k.getVorname());
				LOG.error(e.getMessage());
				throw e;
			}

			// Pruefung, ob ein solcher Kunde schon existiert
			Collection<Kunde> kundenNachname = null;
			try {
				kundenNachname = dao.findKundenByNachname(k.getNachname());
			}
			catch (KundeNotFoundException e) {
				// Es gibt noch keine Kunden mit diesem Nachnamen: OK
			}
			
			if (kundenNachname != null) {
				for (Kunde tmp: kundenNachname) {
					if (tmp.equals(k)) {
						final KundeDuplikatException e = new KundeDuplikatException(k.getId(), k.getNachname(), k.getVorname(), k.getSeit());
						LOG.error(e.getMessage());
						throw e;
					}
				}
			}
			// Jetzt noch Duplikate innerhalb der neuen Kunden pruefen
			for (Kunde tmp: kunden) {
				if (tmp != k && tmp.equals(k)) {
					final KundeDuplikatException e = new KundeDuplikatException(k.getId(), k.getNachname(), k.getVorname(), k.getSeit());
					LOG.error(e.getMessage());
					throw e;
				}
			}
			
			// Password verschluesseln
			passwordVerschluesseln(k);
		}

		dao.insertKunden(kunden);
		return kunden;
	}

	/**
	 * {@inheritDoc}
	 */
	@RolesAllowed({ROLLE_MITARBEITER, ROLLE_ADMIN})  // Eigentlich unsinnig; Security soll halt gezeigt werden
	public Collection<Kunde> updateKunden(Collection<Kunde> kunden) throws KundeDuplikatException, KundeDeletedException, KundeUpdatedException {
		// Werden alle Constraints beim Modifizieren gewahrt?
		// Falls ja: Passwort verschluesseln
		for (Kunde k: kunden) {
			// Pruefung, ob ein solcher Kunde schon existiert
			Collection<Kunde> kundenNachname = null;
			try {
				kundenNachname = dao.findKundenByNachname(k.getNachname());
			}
			catch (KundeNotFoundException e) {
				// Es gibt noch keine Kunden mit diesem Nachnamen: OK
			}
			if (kundenNachname != null) {
				for (Kunde tmp: kundenNachname) {
					if (tmp.equals(k) && tmp.getId().longValue() != k.getId().longValue()) {
						final KundeDuplikatException e = new KundeDuplikatException(k.getId(), k.getNachname(), k.getVorname(), k.getSeit());
						LOG.error(e.getMessage());
						throw e;
					}
				}
			}

			// Jetzt noch Duplikate innerhalb der neuen Kunden pruefen
			for (Kunde tmp: kunden) {
				if (tmp != k && tmp.equals(k)) {
					final KundeDuplikatException e =  new KundeDuplikatException(k.getId(), k.getNachname(), k.getVorname(), k.getSeit());
					LOG.error(e.getMessage());
					throw e;
				}
			}
			
			// Password verschluesseln
			passwordVerschluesseln(k);
		}
		
		kunden = dao.updateKunden(kunden);
		return kunden;
	}
	
	/**
	 * {@inheritDoc}
	 */
	public Kunde neuesBild(Long kundeId, byte[] bildBytes, String extension) throws KundeNotFoundException, KundeInvalidIdException, BildException {
		if (!checkKundeId(kundeId)) {
			final KundeInvalidIdException e = new KundeInvalidIdException(kundeId); 
			LOG.error(e.getMessage());
			throw e;
		}
		
		// Kunde ermitteln und aktualisieren
		Kunde k = dao.findKundeById(kundeId);
		k = dao.updateKundeMitBild(k, bildBytes, extension);

		return k;
	}
	
	/**
	 * {@inheritDoc}
	 */
	public Kunde neuesBild(Long kundeId, File bildTmpFile, String extension) throws KundeNotFoundException, KundeInvalidIdException, BildException {
		if (!checkKundeId(kundeId)) {
			final KundeInvalidIdException e = new KundeInvalidIdException(kundeId); 
			LOG.error(e.getMessage());
			throw e;
		}
		
		// Kunde ermitteln und aktualisieren
		Kunde k = dao.findKundeById(kundeId);
		k = dao.updateKundeMitBild(k, bildTmpFile, extension);

		return k;
	}
	
	/**
	 */
	private boolean checkKundeId(Long kundeId) {
		if (DEBUG) begin(LOG, kundeId);
		
		if (kundeId == null || kundeId.longValue() < 0) {
			if (DEBUG) end(LOG, false);
			return false;
		}
		
		if (DEBUG) end(LOG, true);
		return true;
	}
	
	/**
	 */
	private boolean checkKundeNachname(String nachname) {
		if (DEBUG) begin(LOG, nachname);
		
		if (!nachname.matches(KUNDE_NACHNAME_PATTERN)) {
			if (DEBUG) end(LOG, false);
			return false;
		}
		
		if (DEBUG) end(LOG, true);
		return true;
	}

	
	/**
	 */
	private boolean hasBestellungen(Kunde kunde) {
		if (DEBUG) begin(LOG, kunde);
		
		boolean result = false;
		
		// Gibt es den Kunden und hat er mehr als eine Bestellung?
		// Bestellungen nachladen wegen Hibernate-Caching
		if (kunde != null && kunde.getBestellungen() != null && !kunde.getBestellungen().isEmpty()) {
			result = true;
		}
		
		if (DEBUG) end(LOG, result);
		return result;
	}

	/**
	 */
	private boolean hasAdresse(Kunde kunde) {
		if (DEBUG) begin(LOG, kunde);
		
		boolean result = false;
		
		// Gibt es den Kunden und hat er keine Adresse
		if (kunde != null && kunde.getAdresse() != null)
			result = true;
		
		if (DEBUG) end(LOG, result);
		return result;
	}
	
	private static void passwordVerschluesseln(Kunde kunde) {
		if (DEBUG) begin(LOG, kunde);

		final String unverschluesselt = kunde.getPassword();
		final String verschluesselt = verschluesseln(unverschluesselt);
		kunde.setPassword(verschluesselt);

		if (DEBUG) end(LOG, verschluesselt);
	}

}
